/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;

namespace NTasking.Internals
{

    /// <summary>
    /// The <see cref="SynchronizedObject"/> class is the base of all classes which support Ada concurrency
    /// features (i.e. <see cref="Task"/> and <see cref="Protected"/>).
    /// </summary>
    public abstract class SynchronizedObject
    {

        #region Fields

        /// <summary>
        /// The list of synchronized methods belonging to this instance.
        /// </summary>
        internal readonly List<SynchronizedMethod> SynchronizedMethods = new List<SynchronizedMethod>();


        /// <summary>
        /// The list of all calls pending on any of this instance's entries
        /// </summary>
        internal readonly CallList PendingCalls = new CallList();


        /// <summary>
        /// The stack which stores the entry calls currently being accepted by this instance.
        /// </summary>
        internal readonly Stack<MethodCall> ExecutionStack = new Stack<MethodCall>();


        /// <summary>
        /// A value indicating whether this instance runs in its own thread.
        /// </summary>
        internal readonly bool IsThreaded;

        #endregion


        #region Properties

        /// <summary>
        /// Gets the entry call wich is currently beeing executed.
        /// </summary>
        /// <value>The entry call wich is currently beeing executed.</value>
        internal MethodCall ExecutedCall
        {
            get { return ExecutionStack.Count > 0 ? ExecutionStack.Peek() : null; }
        }


        /// <summary>
        /// Gets or sets the priority of this instance.
        /// </summary>
        /// <value>The priority of this instance.</value>
        public virtual ThreadPriority Priority { get; set; }


        /// <summary>
        /// Gets or sets the name of this instance.
        /// </summary>
        /// <value>The name of this instance.</value>
        public virtual string Name { get; set; }


        /// <summary>
        /// Gets a value indicating whether this instance can perform a requeue.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance can perform a requeue; otherwise, <c>false</c>.
        /// </value>
        internal bool CanRequeue
        {
            get { return ExecutionStack.Count > 0 && ExecutionStack.Peek().Target.IsEntry; }
        }


        /// <summary>
        /// Gets or sets the master <see cref="Block"/> from which this instance has been started.
        /// </summary>
        /// <value>The master.</value>
        internal Block Master { get; set; }

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronizedObject"/> class.
        /// </summary>
        /// <param name="isThreaded">if set to <c>true</c> [is threaded].</param>
        internal SynchronizedObject(bool isThreaded)
        {
            Master = IsStatic() ? Block.Environment : ThreadWrapper.CurrentThread.CurrentBlock();

            if (Master == null)
                throw Errors.NoMasterBlock();

            IsThreaded = isThreaded;

            // Initialize all the entries of this instance
            SynchronizableAttribute.Initialize(this);
        }

        #endregion


        #region Methods


        /// <summary>
        /// Determines whether a <see cref="SynchronizedObject"/>'s constructor is called
        /// from a static method or not.
        /// </summary>
        /// <returns>
        ///     <c>true</c> if the constructor is called from a static method;
        ///     otherwise, <c>false</c>.
        /// </returns>
        internal static bool IsStatic()
        {
            StackTrace stack = new StackTrace(2);

            // Look in the call statck the first occurence of a static method or a method of class Block
            for (int i = 0; i < stack.FrameCount; i++)
            {
                var method = stack.GetFrame(i).GetMethod();

                // If it is a method of class Block, the constructor is not called from a static method
                if (method.DeclaringType == typeof(Block))
                    return false;

                // If it is a static method the constructor is called from a static method
                if (method.IsStatic)
                    return true;
            }

            return false;
        }


        /// <summary>
        /// Find the first rendez vous which can be accepted in the list of pending calls.
        /// </summary>
        /// <returns>The first rendez vous which can be accepted or
        ///              <c>null</c> if no rendez vous can be accepted</returns>
        internal RendezVous FirstRendezVous()
        {
            foreach (MethodCall call in PendingCalls)
            {
                foreach (MethodAccept execution in call.Target.AcceptableCalls)
                {
                    if (execution.IsAcceptable(call))
                        return new RendezVous(call, execution);
                }
            }

            return null;
        }


        /// <summary>
        /// Pushes the execution of the given entry call on the stack of executed calls.
        /// </summary>
        /// <param name="call">The entry call being accepted.</param>
        internal void PushCall(MethodCall call)
        {
            ExecutionStack.Push(call);
        }


        /// <summary>
        /// Pops an entry call from the top of the stack of accepted calls.
        /// </summary>
        internal void PopCall()
        {
            ExecutionStack.Pop();
        }


        /// <summary>
        /// Determine the number of pending calls on a given entry of this instance
        /// </summary>
        /// <param name="entry">The entry for which the call will be retrieved</param>
        /// <returns>
        /// The number of pending calls on the specified entry.
        /// </returns>
        /// <exception cref="NullReferenceException">
        /// Thrown if <paramref name="entry"/> is null.
        /// </exception>
        /// <exception cref="ProgramError">
        /// Thrown if <paramref name="entry"/> is not an entry of this instance.
        /// </exception>
        public static int Count(Delegate entry)
        {
            if (entry == null)
                throw Errors.EntryIsNull("Count");

            var executable = entry.Target as IExecutable;

            // Check that entry is a synchronisez method
            if (executable == null)
                throw Errors.NotASynchronizedMethod();

            // Check that entry is an entry and not a procedure/function
            if (!executable.Family.IsEntry)
                throw Errors.NotAnEntry(executable.Family);

            MethodAccept context = executable.CreateAccept();

            // Count the number of pending calls
            return executable.Family.Owner.PendingCalls.MatchingCount(context.MatchCall);
        }


        /// <summary>
        /// Perform a synchronous call of an entry or protected procedure or function.
        /// </summary>
        /// <typeparam name="RetT">The type of the call result.</typeparam>
        /// <typeparam name="ExecuteDelegateT">A delegate type representing the call's execution profile.</typeparam>
        /// <param name="call">The call.</param>
        /// <returns></returns>
        internal abstract RetT SynchronousCall<RetT, ExecuteDelegateT>(MethodCall call);


        /// <summary>
        /// Perform a synchronous call of an entry or protected procedure or function.
        /// </summary>
        /// <typeparam name="RetT">The type of the call result.</typeparam>
        /// <param name="call">The call.</param>
        /// <returns></returns>
        internal Future<RetT> AynchronousCall<RetT>(MethodCall<RetT> call)
        {

            // Check that this instance can be blocked since an entry call is an Ada
            // 'potentially blocking operation'
            if (!call.Thread.CanBlock)
                throw Errors.PotentiallyBlockingOperationNotAllowed("Entry call");

            PerformCall("Asynchronous entry call ", call, () =>
            {
                EntryCall(call, false);
            });

            return new Future<RetT>(call);
        }


        /// <summary>
        /// Perform a synchronous call of an entry.
        /// </summary>
        /// <param name="call">The entry call to execute.</param>
        internal void SynchronousEntryCall(MethodCall call)
        {

            // Check that this instance can be blocked since an entry call is an Ada
            // 'potentially blocking operation'
            if (!call.Thread.CanBlock)
                throw Errors.PotentiallyBlockingOperationNotAllowed("Entry call");

            PerformCall("Synchronous entry call ", call, () =>
            {
                EntryCall(call, false);
                if (!call.Finished)
                    SuspendCaller(call, DateTime.MaxValue, null);
            });
        }


        /// <summary>
        /// Perform a selective call of an entry with a delay alternative.
        /// </summary>
        /// <param name="call">The entry call to execute.</param>
        /// <param name="timeout">The timeout to wait for.</param>
        /// <return>The call which has been selected or <c>null</c> if the timeout occured</return>
        internal static void TimedEntryCall(MethodCall call, DateTime timeout)
        {
            SelectiveCall("Timed entry calls ", call, false, timeout, null);
        }


        /// <summary>
        /// Perform a selective call of an entry with an abortable part.
        /// </summary>
        /// <param name="call">The entry call to execute.</param>
        /// <param name="abortablePart">The abortable part.</param>
        /// <return>The call which has been selected or <c>null</c> if the abortable part completed</return>
        internal static void AsynchronousSelect(MethodCall call, Action abortablePart)
        {
            SelectiveCall("Entry calls with abortable part ", call, false, DateTime.MaxValue, abortablePart);
        }


        /// <summary>
        /// Perform a selective call of an entry with a else alternative.
        /// </summary>
        /// <param name="call">The entry call to  execute.</param>
        /// <return>The call which has been selected or <c>null</c> if the else alternative has been selected</return>
        internal static void ConditionalEntryCall(MethodCall call)
        {
            SelectiveCall("Conditional entry calls ", call, true, DateTime.MaxValue, null);
        }


        /// <summary>
        /// Perform a call on an entry.
        /// </summary>
        /// <param name="kind">A string describing the kind of calls performed.</param>
        /// <param name="calls">The entry calls to be performed</param>
        /// <param name="tryOnly">if set to <c>true</c> perform the call only if it can be accepted immediately.</param>
        /// <param name="timeout">The timeout, if the call is a timed entry call, or <c>DateTime.MaxValue</c> otherwise.</param>
        /// <param name="abortablePart">The abortable part, if the call is a call with an abortable part; <c>null</c> otherwise.</param>
        /// <return>The call which has been selected or <c>null</c> if another alternative has been selected</return>
        internal static void SelectiveCall(string kind, MethodCall calls, bool tryOnly, DateTime timeout, Action abortablePart)
        {
            PerformCall(kind, calls, () =>
            {
                // Enqueue (or try to enqueue) the call
                calls.Target.Owner.EntryCall(calls, tryOnly);

                if (!calls.Finished && !tryOnly)
                    // Wait until the call is finished or the timeout is reached
                    // or the abortable part finishes
                    SuspendCaller(calls, timeout, abortablePart);
            });
        }


        /// <summary>
        /// Perform a synchronized method call.
        /// </summary>
        /// <param name="kind">A string describing the kind of calls performed.</param>
        /// <param name="call">The synchronized method call to be performed</param>
        /// <param name="performCall">A delegate representing the call execution.</param>
        private static void PerformCall(string kind, MethodCall call, Action performCall)
        {
            ThreadWrapper currentThread = ThreadWrapper.CurrentThread;

            using (currentThread.NoAbort())
            {
                try
                {
                    Tracer.Trace(kind + call + " started");

                    using (currentThread.CallLock.Locked())
                    {
                        performCall();
                    }

                }
                // TODO: remove this catch block???
                catch (ThreadAbortException ex)
                {
                    if (ThreadWrapper.IsIllegalAbort(ex))
                        Errors.FatalError("Using Thread.Abort is not allowed when using the nTasking library.");
                }
                finally
                {
                    Tracer.Trace("Entry call " + call +
                        (call.Context.Exception != null ? (" threw exception " + call.Context.Exception.GetType()) :
                         call.Context.ResultIsSet ? " finished normally" :
                         " cancelled"));
                }
            }
        }


        /// <summary>
        /// Perform a call on an entry.
        /// </summary>
        /// <param name="call">The entry call to be performed</param>
        /// <param name="tryOnly">if set to <c>true</c> perform the call only if it can be accepted immediately.</param>
        internal abstract void EntryCall(MethodCall call, bool tryOnly);


        /// <summary>
        /// Perform a requeued of the currently accepted call.
        /// This method is executed within the context of the task/protected type in which the
        /// requeue statement appears.
        /// </summary>
        /// <param name="requeuedCall">The method onto which the requeue is to be performed</param>
        /// <param name="withAbort">Indicate whether the requeue statement is 'with abort' or not.</param>
        private void Requeue(Delegate requeuedCall, bool withAbort)
        {
            if (requeuedCall == null)
                throw Errors.RequeueTargetIsNull(this);

            SynchronizedMethod requeuedMethod = requeuedCall.Target as SynchronizedMethod;

            // Check that the requeue is performed from within an Accept statement
            if (!CanRequeue)
                throw Errors.RequeueWhenNotInEntry(this);

            // Check that the target of the requeue is not null
            if (requeuedMethod == null)
                throw Errors.RequeueTargetIsNotSynchronized(this);

            // Check that the target of the requeue is an entry
            if (!requeuedMethod.IsEntry)
                throw Errors.RequeueTargetIsNotAnEntry(requeuedMethod);

            // Get the call which will be requeued to the new target
            MethodCall original = ExecutedCall;

            MethodCall requeue = ((ICallableBase)requeuedMethod).CreateCall();

            // Check that the requeue is not on the same entry as the original call
            if (original.TargetInstance.IsEqual(requeue.TargetInstance))
                throw Errors.RequeueOnSameEntry(requeue.Target);

            // Check that the requeue target has a parameter and return type profile
            // compatible with the original call
            if (!requeue.Target.IsRequeueable(original.Target))
                throw Errors.RequeueProfileMismatch(original, requeue);

            using (ThreadWrapper.CurrentThread.NoAbort())
            {
                // Mark the call as cancellable if withAbort is true
                original.Cancelable = withAbort;

                bool internalRequeue = original.Target.Owner == requeue.Target.Owner;

                // Check if the "abortability" of the entry call has changed
                if (withAbort && original.Thread.AbortRequested)
                    // Check whether an abort of the calling task is pending,
                    // If yes no use to continue, throw an exception to change the program flow
                    ThreadWrapper.Requeue(null);

                // Perform the requeue
                MethodAccept requeued = requeue.Target.Owner.Requeue(original, requeue, internalRequeue);

                // Throw an exception to change the program flow
                ThreadWrapper.Requeue(internalRequeue ? requeued : null);
            }
        }


        /// <summary>
        /// Requeues an entry call to a new entry call.
        /// This method is executed within the context of the task/protected type declaring the entry
        /// onto which the call is requeued.
        /// </summary>
        /// <param name="call">The call to be requeued</param>
        /// <param name="requeued">The entry to which the call is requeued</param>
        /// <param name="internalRequeue">
        /// Indicate whether the requeue is an internal requeue or an external requeue
        /// </param>
        internal abstract MethodAccept Requeue(MethodCall call, MethodCall requeued, bool internalRequeue);


        /// <summary>
        /// Cancel a pending call to a synchronized operation
        /// </summary>
        /// <param name="call">The call to cancel</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal abstract void Cancel(MethodCall call, bool signalCaller);


        /// <summary>
        /// Finishes a synchronized method call.
        /// </summary>
        /// <param name="call">The call to  finish.</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal abstract void Finish(MethodCall call, bool signalCaller);


        /// <summary>
        /// Finishes a synchronized method call.
        /// </summary>
        /// <param name="call">The call to  finish.</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal virtual void DoFinish(MethodCall call, bool signalCaller)
        {
            if (call.Finished)
                return;

            // Do everything in a finally block to avoid abortion
            try { }
            finally
            {
                // Remove the call from the queue of pending calls
                PendingCalls.Remove(call);

                // Mark the call as finished
                call.Finished = true;

                // Resume the caller if necessary
                if (signalCaller && ThreadWrapper.CurrentThread != call.Thread)
                    call.CallLock.Pulse();

                // Mark the call as cancelable
                call.Cancelable = true;
            }
        }


        /// <summary>
        /// Executes the abortable part of a selective call.
        /// </summary>
        /// <param name="call">The selective calls.</param>
        /// <param name="abortablePart">The abortable part.</param>
        private static void ExecuteAbortablePart(MethodCall call, Action abortablePart)
        {
            Exception exception = null;
            bool aborted = false;

            // The abortable part of the call must be executed
            Tracer.Trace("Starting abortable part of " + call);

            // Temporarily release the lock on the call
            using (call.CallLock.Unlocked())
            {
                try
                {
                    call.Thread.AbortResume();
                    abortablePart();
                    call.Thread.AbortDefer();
                }
                catch (ThreadAbortException ex)
                {
                    // The abortable part has been aborted

                    call.Thread.AbortDefer();

                    call.WaitState = CallerWaitState.NoWait;

                    aborted = true;

                    if (ThreadWrapper.IsTaskInterrupt(ex, call))
                    {
                        // If it is an interruption of the abortable part because the call has been
                        // accepted, prevent the exception to further propagate auttomatically
                        Tracer.Trace("Abortable part of " + call + " interrupted");
                        Thread.ResetAbort();
                    }
                }
                catch (Exception ex)
                {
                    call.Thread.AbortDefer();
                    ex.PreserveStackTrace(true);
                    exception = ex;
                }
            }

            using (call.Thread.WithAbort())
            {
                // This must be performed outside any catch/finally block to avoid deadlock
                lock (call.Thread.WaitStateLock)
                {
                    if (aborted)
                        // Abortable part was aborted
                        call.WaitState = CallerWaitState.WaitForCompletion;
                    else
                    {
                        // Abortable part finished (either normally or upon exception)

                        call.WaitState = CallerWaitState.NoWait;

                        if (exception != null)
                            // Abortable part threw exception, rethrow it
                            throw exception;

                        return;
                    }
                }
            }

            // Abortable part has been cancelled, wait for call completion
            WaitForCompletion(call);
        }


        /// <summary>
        /// Waits for the selective call to complete or the timeout to occur.
        /// </summary>
        /// <param name="call">The selective call.</param>
        /// <param name="timeout">The timeout.</param>
        private static void WaitForTimeout(MethodCall call, DateTime timeout)
        {
            // Wait until expiration of the call's timeout or until call completion

            Tracer.Trace("Wait for timeout or completion of " + call);

            using (call.Thread.WithAbort())
            {
                bool timeoutOccured = !call.CallLock.WaitLock(timeout);

                lock (call.Thread.WaitStateLock)
                {
                    if (call.Finished || timeoutOccured)
                    {
                        // Call is finished or timeout occured
                        call.WaitState = CallerWaitState.NoWait;
                        return;
                    }

                    // Timeout has been cancelled
                    call.WaitState = CallerWaitState.WaitForCompletion;
                }
            }

            // Timeout has been cancelled, wait for call completion
            WaitForCompletion(call);
        }


        /// <summary>
        /// Waits for completion of the call.
        /// </summary>
        /// <param name="call">The call.</param>
        private static void WaitForCompletion(MethodCall call)
        {
            // Wait for the call completion

            Tracer.Trace("Wait for completion of " + call);

            while (!call.Finished)
            {
                using (call.Thread.WithAbort())
                {
                    call.CallLock.WaitLock();
                }
            }

            call.WaitState = CallerWaitState.NoWait;
        }


        /// <summary>
        /// Suspends the caller task until the corresponding entry is finished or has been cancelled or aborted.
        /// </summary>
        /// <param name="call">The entry call for which completion to wait.</param>
        /// <param name="timeout">The timeout associated with the entry call or <c>DateTime.MaxValue</c>.</param>
        /// <param name="abortablePart">The abortable part associated with the entry call or <c>null</c>.</param>
        internal static void SuspendCaller(MethodCall call, DateTime timeout, Action abortablePart)
        {
            ThreadWrapper thread = call.Thread;

            try
            {
                thread.StartWaitingForCompletion(call);

                // Compute the initial state of the state machine

                lock (thread.WaitStateLock)
                {
                    if (abortablePart != null && (!call.IsRequeued || call.Target.Owner == Delay.Instance))
                        // The call has an abortable part and has not been already requeued
                        // Special case is treated here for calls to Delay task in order to force
                        // execution of the abortable part in constructs select/delay/then bort.
                        call.WaitState = CallerWaitState.ExecuteAbortablePart;
                    else if (timeout != DateTime.MaxValue && !call.IsRequeued)
                        // The call has a timeout and has not been already requeued
                        call.WaitState = CallerWaitState.WaitForTimeout;
                    else
                        // The call is a direct entry call or has already been requeued
                        call.WaitState = CallerWaitState.WaitForCompletion;
                }

                switch (call.WaitState)
                {

                    case CallerWaitState.ExecuteAbortablePart:

                        ExecuteAbortablePart(call, abortablePart);
                        break;

                    case CallerWaitState.WaitForTimeout:

                        WaitForTimeout(call, timeout);
                        break;

                    default:

                        WaitForCompletion(call);
                        break;
                }
            }
            finally
            {
                call.Target.Owner.Cancel(call, false);
                call.WaitState = CallerWaitState.NoWait;
                thread.EndWaitingForCompletion();
            }
        }


        /// <summary>
        /// Signal that a call has had it's caller task priority lowered
        /// </summary>
        /// <param name="call">The call for which the priority has been lowered</param>
        internal abstract void PriorityLowered(MethodCall call);


        /// <summary>
        /// Signal that a call has had it's caller task priority raised
        /// </summary>
        /// <param name="call">The call for which the priority has been raised</param>
        internal abstract void PriorityRaised(MethodCall call);


        /// <summary>
        /// Converts the object into a string.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string name = Name;

            return GetType().Name + (string.IsNullOrEmpty(name) ? "" : "[" + name + "]");
        }


        /// <summary>
        /// Requeues the currently accepted call to the specified entry.
        /// </summary>
        /// <param name="requeuedCall">The entry which is the target of the requeue.</param>
        // ReSharper disable InconsistentNaming
        protected void Requeue(Delegate requeuedCall)
        // ReSharper restore InconsistentNaming
        {
            Requeue(requeuedCall, false);
        }


        /// <summary>
        /// Requeues the currently accepted call to the specified entry, allowing caller's abort or call cancellation.
        /// </summary>
        /// <param name="requeuedCall">The entry which is the target of the requeue.</param>
        // ReSharper disable InconsistentNaming
        protected void RequeueWithAbort(Delegate requeuedCall)
        // ReSharper restore InconsistentNaming
        {
            Requeue(requeuedCall, true);
        }

        #endregion Methods

    }
}
